# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Llm::VertexAi::Completions::ExplainVulnerability, feature_category: :vulnerability_management do
  let(:prompt_class) { Gitlab::Llm::Templates::ExplainVulnerability }
  let_it_be(:user) { create(:user) }
  let_it_be(:project) do
    create(:project, :custom_repo, files: {
      'main.c' => "#include <stdio.h>\n\nint main() { printf(\"hello, world!\"); }"
    })
  end

  let_it_be(:vulnerability) { create(:vulnerability, :with_finding, project: project) }

  subject(:explain) { described_class.new(prompt_class, { request_id: 'uuid' }) }

  before do
    allow(GraphqlTriggers).to receive(:ai_completion_response)
    vulnerability.finding.location['file'] = 'main.c'
    vulnerability.finding.location['start_line'] = 1
  end

  describe '#execute', :clean_gitlab_redis_cache do
    context 'when the feature flag is disabled' do
      before do
        stub_feature_flags(explain_vulnerability_vertex: false)
      end

      it 'falls back to the OpenAI implementation' do
        options = {}

        allow_next_instance_of(::Gitlab::Llm::OpenAi::Completions::ExplainVulnerability) do |completion|
          expect(completion).to receive(:execute).with(user, vulnerability, options)
        end

        explain.execute(user, vulnerability, options)

        expect(GraphqlTriggers).not_to have_received(:ai_completion_response)
      end
    end

    context 'when the chat client returns an unsuccessful response' do
      before do
        allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
          allow(client).to receive(:chat).and_return(
            { error: { message: 'Ooops...' } }.to_json
          )
        end
      end

      it 'publishes the error to the graphql subscription' do
        explain.execute(user, vulnerability, {})

        expect(GraphqlTriggers).to have_received(:ai_completion_response)
          .with(user.to_global_id, vulnerability.to_global_id, hash_including({
            id: anything,
            model_name: vulnerability.class.name,
            response_body: '',
            request_id: 'uuid',
            errors: ['Ooops...']
          }))
      end
    end

    context 'when the chat client returns a successful response' do
      let(:example_answer) { "Sure, ..." }

      let(:example_response) do
        {
          "predictions" => [
            {
              "candidates" => [
                {
                  "author" => "",
                  "content" => example_answer
                }
              ],
              "safetyAttributes" => {
                "categories" => ["Violent"],
                "scores" => [0.4000000059604645],
                "blocked" => false
              }
            }
          ],
          "deployedModelId" => "1",
          "model" => "projects/1/locations/us-central1/models/codechat-bison-001",
          "modelDisplayName" => "codechat-bison-001",
          "modelVersionId" => "1"
        }
      end

      before do
        allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
          allow(client).to receive(:chat).and_return(example_response.to_json)
        end
      end

      it 'publishes the content from the AI response' do
        explain.execute(user, vulnerability, {})

        expect(GraphqlTriggers).to have_received(:ai_completion_response)
          .with(user.to_global_id, vulnerability.to_global_id, hash_including({
            id: anything,
            model_name: vulnerability.class.name,
            response_body: example_answer,
            request_id: 'uuid',
            errors: []
          }))
      end

      context 'when an unexpected error is raised' do
        let(:error) { StandardError.new("Ooops...") }

        before do
          allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
            allow(client).to receive(:chat).and_raise(error)
          end
          allow(Gitlab::ErrorTracking).to receive(:track_exception)
        end

        it 'records the error' do
          explain.execute(user, vulnerability, {})
          expect(Gitlab::ErrorTracking).to have_received(:track_exception).with(error)
        end

        it 'publishes a generic error to the graphql subscription' do
          explain.execute(user, vulnerability, {})

          expect(GraphqlTriggers).to have_received(:ai_completion_response)
            .with(user.to_global_id, vulnerability.to_global_id, hash_including({
              id: anything,
              model_name: vulnerability.class.name,
              response_body: '',
              request_id: 'uuid',
              errors: [described_class::DEFAULT_ERROR]
            }))
        end
      end

      context 'when request is cached', :use_clean_rails_redis_caching do
        before do
          allow_next_instance_of(Gitlab::Llm::VertexAi::Client) do |client|
            allow(client).to receive(:chat).once.and_return(example_response.to_json)
          end

          explain.execute(user, vulnerability, {})
        end

        it 'executes the request just once' do
          expect(Gitlab::Llm::VertexAi::Client).not_to receive(:new)

          explain.execute(user, vulnerability, {})

          expect(GraphqlTriggers).to have_received(:ai_completion_response)
            .with(user.to_global_id, vulnerability.to_global_id, hash_including({
              id: anything,
              model_name: vulnerability.class.name,
              response_body: example_answer,
              request_id: 'uuid',
              errors: []
            })).twice
        end
      end
    end
  end
end
